﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Threading;
using System.Drawing;

namespace Tzeentch
{    
    class Controler
    {
        delegate void RefreshGuiDelegate(bool onlyProgressBar);

        #region Constants
    
        private const int _selectionFactor = 2;
        private const int _randomnessFactor = 10;

        #endregion

        #region Fields
        RefreshGuiDelegate _refreshGUIDelegate;
        private GUI _gui;
        private Thread _mainLoopThread;
        private Collection<Mosaic> _population;
        private Collection<Mosaic> _previousPopulation;
        private int _sizeOfPopulation;
        private int _currentIteration;
        private bool _mainLoopPauseFlag;
        private bool _mainLoopStopFlag;
        private int _progress;
        private Random rand;
        private DateTime _iterationStartTime;
        private TimeSpan _lastIterationComputingTime;
        private Logger logger = null;
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the number of iterations.
        /// </summary>
        /// <value>
        /// The number of iterations.
        /// </value>
        public int NumberOfIterations { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether visualisation is enabled.
        /// </summary>
        /// <value>
        ///   <c>true</c> if visualisation is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool EnableVisualisation {get;set;}
        #endregion

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="Controler"/> class.
        /// </summary>
        /// <param name="gui">The GUI.</param>
        public Controler(GUI gui)
        {
            _gui = gui;
            logger = new Logger();
        }
        #endregion

        #region Methods
        /// <summary>
        /// Initialises this instance.
        /// </summary>
        public void Initialise()
        {
            rand = new Random();

            //initialise variables
            _mainLoopPauseFlag = false;
            _mainLoopStopFlag = false;
            _currentIteration = 1;
            NumberOfIterations = 1;
            _sizeOfPopulation = 1;

            SetupGUI();
            _refreshGUIDelegate = delegate(bool onlyProgressBar)
            {
                RefreshGUI(onlyProgressBar);
            };

            _population = new Collection<Mosaic>();
            _previousPopulation = new Collection<Mosaic>();
            
            _mainLoopThread = new Thread(MainLoop);
        }

        /// <summary>
        /// Setups the GUI.
        /// </summary>
        public void SetupGUI()
        {            
            _gui.progressBarAll.Maximum = NumberOfIterations;

            _gui.startStopButton.Click += (sender, args) =>
            {
                StartStopButtonClicked();              
            };

            _gui.buttonPause.Click += (sender, args) =>
            {
                _mainLoopPauseFlag = !_mainLoopPauseFlag;
            };

            _gui.FormClosing += (sender, args) =>
            {
                StopThreads();
            };

            _gui.numericUpDownNumberOfIterations.ValueChanged += (sender, args) =>
            {
                NumberOfIterations = (int)_gui.numericUpDownNumberOfIterations.Value;
                _gui.progressBarAll.Maximum = NumberOfIterations;
            };

            _gui.numericUpDownPopulationSize.ValueChanged += (sender, args) =>
            {
                _sizeOfPopulation = (int)_gui.numericUpDownPopulationSize.Value;
            };

            _gui.checkBoxInfinity.CheckedChanged += (sender, args) =>
               {
                   if (_gui.checkBoxInfinity.Checked)
                   {
                       NumberOfIterations = -1;
                       _gui.progressBarAll.Enabled = false;
                   }
                   else
                   {
                       NumberOfIterations = (int)_gui.numericUpDownNumberOfIterations.Value;
                       _gui.progressBarAll.Enabled = true;
                       _gui.progressBarAll.Maximum = NumberOfIterations;
                   }
               };
        }

        /// <summary>
        /// Progresses the GUI.
        /// </summary>
        /// <param name="progress">The progress.</param>
        /// <param name="refreshProgressBar">if set to <c>true</c> [refresh progress bar].</param>
        void ProgressGUI(int progress, bool refreshProgressBar)
        {
            _progress = progress;
            _refreshGUIDelegate(refreshProgressBar);
        }

        /// <summary>
        /// Stops the threads.
        /// </summary>
        public void StopThreads()
        {
            _mainLoopThread.Abort();
        }
        /// <summary>
        /// Starts the main loop.
        /// </summary>
        public void MainLoop()
        {            
            while (!(NumberOfIterations > 0 && _currentIteration >= NumberOfIterations) && ! _mainLoopStopFlag)
            {
                if (_mainLoopPauseFlag || _gui.OriginalImage == null)
                {
                    Thread.Sleep(100);
                    continue;
                }
                _iterationStartTime = DateTime.Now;

                ProgressGUI(0, true);

                GenerateBitmaps();

                ProgressGUI(2500, true);

                CountFitness();

                LogFitness();
                Selection();

                ProgressGUI(2500, false);
                ProgressGUI(5000, true);

                Cross(); // also counting fitness inside

                ProgressGUI(7500, true);

                MutatePopulation();
                GenerateBitmaps();
                _lastIterationComputingTime = DateTime.Now - _iterationStartTime;

                ProgressGUI(10000, true);

                _currentIteration++;

                //GC.Collect();
                Thread.Sleep(100);
            }
            GenerateBitmaps();
            LogFitness();
            _mainLoopStopFlag = true;
            _refreshGUIDelegate(false);
        }

        /// <summary>
        /// Logs fitness of population to a file
        /// </summary>
        private void LogFitness()
        {
            List<double> fitnessOfAll = new List<double> { };

            foreach (Mosaic m in _population)
            {
                fitnessOfAll.Add(m.Fitness);
            }

            logger.CountLogs(_currentIteration, fitnessOfAll.ToArray());
        }

        /// <summary>
        /// Cross members of populations with highest fitness factor.
        /// </summary>
        public void Cross()
        {
            // the whole iterations don't make sense for population = 1.
            if (_population.Count == 1)
                return;

            _previousPopulation.Clear();
            foreach (var item in _population)
            {
                _previousPopulation.Add(item);
            }

            Collection<Mosaic> bestFittedMosaics = new Collection<Mosaic>();

            for (int i = 0; i < _selectionFactor; i++)
            {
                bestFittedMosaics.Add((from item in _population where item.Fitness >= _population.Max(m => m.Fitness) select item).FirstOrDefault());
                _population.Remove(bestFittedMosaics.Last());
            }

            Mosaic crossed = new Mosaic(_gui.OriginalImage);
            crossed.Polygons.Clear();

            for (int i = 0; i < bestFittedMosaics.Max(m => m.Polygons.Count); i++)
            {
                int winner = rand.Next(_selectionFactor);
                if (bestFittedMosaics[winner].Polygons.Count > i)
                {
                    crossed.Polygons.Add(bestFittedMosaics[winner].Polygons[i]);
                }
            }
            crossed.RenderBitmap();
            crossed.CountFitness();

            //experimental
            //if (crossed.Fitness < bestFittedMosaics.First().Fitness)
                //crossed = bestFittedMosaics.First();

            
            _population.Clear();
            for (int i = 0; i < _sizeOfPopulation - _randomnessFactor; i++)
            {             
                _population.Add(new Mosaic(crossed));
            }

            for (int i = 0; i < _randomnessFactor; i++)
            {
                _population.Add(new Mosaic((_gui.OriginalImage)));
            } 
        }

        /// <summary>
        /// Copies elements that are better fitted then the best one in previous population
        /// </summary>
        public void Selection()
        {
            if (!_previousPopulation.Any() || !_population.Any())
                return;

            Collection<Mosaic> newPopulation = new Collection<Mosaic>();
            var orderedPopulation = _previousPopulation.OrderBy(f => f.Fitness).ToList();
            Mosaic previousBest = new Mosaic(orderedPopulation.Last());
            previousBest.Fitness = orderedPopulation.Last().Fitness;

            foreach (var item in _population)
            {
                if (!orderedPopulation.Any())
                    break;

                if (item.Fitness > orderedPopulation.Last().Fitness)
                {
                    newPopulation.Add(item);
                }
                else
                {
                    newPopulation.Add(orderedPopulation.Last());
                    _previousPopulation.Remove(orderedPopulation.Last());
                    orderedPopulation = _previousPopulation.OrderBy(f => f.Fitness).ToList();
                }
            }
            _population = new Collection<Mosaic>(newPopulation);
        }

        /// <summary>
        /// Mutates the population.
        /// </summary>
        public void MutatePopulation()
        {
            if (_mainLoopStopFlag)
                return;

            Mosaic original = _population.First();

            foreach (var item in _population)
            {
                item.Mutate();
                //item.RenderBitmap();
                //item.CountFitness();
            }

            _population.Add(original);
        }

        /// <summary>
        /// Generates the bitmaps.
        /// </summary>
        public void GenerateBitmaps()
        {
            foreach (var item in _population)
            {
                item.RenderBitmap();
            }
        }

        /// <summary>
        /// Counts the fitness.
        /// </summary>
        public void CountFitness()
        {
            foreach (var item in _population)
            {
                    item.CountFitness();                    
                _progress += (int)2500 / _population.Count;
                _refreshGUIDelegate(true);
            }
        }

        /// <summary>
        /// Refreshes the GUI.
        /// </summary>
        public void RefreshGUI(bool onlyProgressBar)
        {
            if (onlyProgressBar)
            {
                _gui.progressBar1.Value = _progress;
                return;
            }
            if (_population == null || !_population.Any())
                return;

            Mosaic bestFited = (from item in _population where item.Fitness >= _population.Max(m => m.Fitness) select item).FirstOrDefault();
            _gui.GeneratedImage = bestFited.GeneratedImage;
            _gui.labelBestFittnes.Text = bestFited.Fitness.ToString();
            _gui.labelIteration.Text = _currentIteration.ToString();

            

            if (_mainLoopPauseFlag)
            {
                _gui.labelStatus.Text = "Paused";
                _gui.labelStatus.ForeColor = Color.DarkGray;
                _gui.buttonPause.Text = "Continue";
            }
            else
            {
                _gui.labelStatus.Text = "Started";
                _gui.labelStatus.ForeColor = Color.Green;
                _gui.buttonPause.Text = "Pause";
            }

            if (_mainLoopStopFlag)
            {
                _gui.labelStatus.Text = "Stopped";
                _gui.labelStatus.ForeColor = Color.Red;
                _gui.startStopButton.Text = "Start";
                _gui.groupBoxSettings.Enabled = true;
            }

            if(_gui.progressBarAll.Enabled)  
                _gui.progressBarAll.Value = _currentIteration;

            _gui.labelComputingTime.Text = _lastIterationComputingTime.ToString();
            _gui.RefreshGUI();
        }

        /// <summary>
        /// Creates the new population.
        /// </summary>
        public void CreateNewPopulation()
        {
            _population.Clear();
            _previousPopulation.Clear();
            for (int i = 0; i < _sizeOfPopulation; i++)
            {
                _population.Add(new Mosaic(_gui.OriginalImage));
            }
        }
        #endregion

        #region GUI actions
        /// <summary>
        /// Start/Stop button clicked.
        /// </summary>
        private void StartStopButtonClicked()
        {
            if (_gui.OriginalImage == null)
                return;

            if (_mainLoopThread.ThreadState == ThreadState.Unstarted || _mainLoopThread.ThreadState == ThreadState.Stopped)
            {
                CreateNewPopulation();
                _currentIteration = 0;
                logger.SetAmountOfElementHeaders(_sizeOfPopulation);
                _mainLoopStopFlag = false;
                if (_mainLoopThread != null)
                    _mainLoopThread = new Thread(MainLoop);
                _mainLoopThread.Start();
                _gui.labelStatus.Text = "Started";
                _gui.labelStatus.ForeColor = Color.Green;
                return;
            }

            _mainLoopStopFlag = true;
        }
        #endregion
    }
}
